﻿using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.ProjectSystem.Properties;

namespace Red.Scripts.Project.Utility
{
    internal static class CommandlineBuilder
    {
        #region Private Types

        /// <summary>
        /// A collection of command line arguments that can be resolved into a single string to be passed as the command line when starting a new process
        /// </summary>
        private class Arguments
        {
            #region Prvate Types

            private class Argument
            {
                public string Name { get; set; }
                public string Prefix { get; set; }

                public override string ToString()
                {
                    return $"{Prefix}{Name}";
                }
            }

            private class ValuedArgument : Argument
            {
                public string Value { get; set; }

                public override string ToString()
                {
                    return $"{Prefix}{Name}=\"{Value}\"";
                }
            }

            #endregion Prvate Types

            #region Private Data

            private const string DefaultPrefix = "-";

            private List<Argument> _arguments = new List<Argument>();

            #endregion Private Data

            #region Public Properties

            public string Formatted => string.Join( " ", _arguments );

            #endregion Public Properties

            #region Public Methods

            public void AddArgument( string argument, string value, string prefix = DefaultPrefix )
            {
                _arguments.Add( new ValuedArgument() { Name = argument, Value = value, Prefix = prefix } );
            }

            public void AddSwitch( string argument, string prefix = DefaultPrefix )
            {
                _arguments.Add( new Argument() { Name = argument, Prefix = prefix } );
            }

            #endregion Public Methods
        }

        #endregion Private Types

        #region Public Functions

        public static async Task<string> ConstructLaunch( ProjectProperties properties )
        {
            DefinesAccessor defines = new DefinesAccessor();
            await defines.Load();

            return await ConstructLaunch( properties, defines );
        }

        private static string GetIniVarValue(string variable)
        {
            return variable.Substring(variable.IndexOf("=") + 1);
        }

        public static async Task<string> ConstructLaunch( ProjectProperties properties, DefinesAccessor defines )
        {
            Arguments arguments = new Arguments();

            ConstructRoots(defines, arguments);

            string appDataPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
            string configIni = "RedEngine.common.ini";
            string line;

            string configPath = System.IO.Path.Combine(appDataPath, "CD Projekt Red", "Red Engine", configIni);

            System.IO.StreamReader file = new System.IO.StreamReader(configPath);

            bool fullscreen = false;
            bool hdr = false;

            while ((line = file.ReadLine()) != null)
            {
                if (line.StartsWith("Display="))
                {
                    string value = GetIniVarValue(line);
                    if (value == "Window")
                    {
                        arguments.AddSwitch("window");
                        arguments.AddSwitch("windowed");
                    }
                    else if (value == "Borderless Window")
                    {
                        arguments.AddSwitch("borderless");
                    }
                    else if (value == "Fullscreen")
                    {
                        arguments.AddSwitch("fullscreen");
                        fullscreen = true;
                    }
                }
                else if (line.StartsWith("Resolution="))
                {
                    string[] values = GetIniVarValue(line).Split(' ');
                    arguments.AddArgument("width", values[0]);
                    arguments.AddArgument("height", values[2]);
                }
                else if (line.StartsWith("VO\\ Lang="))
                {
                    arguments.AddArgument("voLanguage", GetIniVarValue(line));
                }
                else if (line.StartsWith("Subs\\ Lang="))
                {
                    arguments.AddArgument("subtitleLanguage", GetIniVarValue(line));
                }
                else if (line.StartsWith("Text\\ Lang="))
                {
                    arguments.AddArgument("textLanguage", GetIniVarValue(line));
                }
                else if (line.StartsWith("Quality="))
                {
                    arguments.AddArgument("qualityLevel", GetIniVarValue(line));
                }
                else if (line.StartsWith("Texture\\ Quality="))
                {
                    arguments.AddArgument("texQualityLevel", GetIniVarValue(line));
                }
                else if (line.StartsWith("Vsync\\ Interval="))
                {
                    string value = GetIniVarValue(line);
                    if ( value == "no Vsync" )
                    {
                        arguments.AddArgument("vsyncInterval", "0" );
                    }
                    else if (value == "60 fps (each VBlank )")
                    {
                        arguments.AddArgument("vsyncInterval", "1");
                    }
                    else if (value == "30 fps (every 2nd VBlank )")
                    {
                        arguments.AddArgument("vsyncInterval", "2");
                    }
                    else if (value == "20 fps (every 3rd VBlank )")
                    {
                        arguments.AddArgument("vsyncInterval", "3");
                    }
                    else if (value == "15 fps (every 4th VBlank )")
                    {
                        arguments.AddArgument("vsyncInterval", "4");
                    }
                }
                else if (line.StartsWith("Player\\ Gender="))
                {
                    arguments.AddArgument("playerGender", GetIniVarValue(line));
                }
                else if (line.StartsWith("Censorship\\ Region="))
                {
                    arguments.AddArgument("region", GetIniVarValue(line).Split(' ')[0]);
                }
                else if (line.StartsWith("Dynamic\\ Range="))
                {
                    if (GetIniVarValue(line) == "HDR")
                        hdr = true;
                }
                else if (line.StartsWith("Script\\ Version"))
                {
                    arguments.AddArgument("scriptVersion", GetIniVarValue(line));
                }
            }

            if ( fullscreen && hdr)
            {
                arguments.AddSwitch("hdr");
            }

            await AddGameDef("gameDefinition", properties, defines, arguments);

            var debuggerProperties = await properties.GetRedScriptsDebuggerPropertiesAsync();
            await AddArgument( debuggerProperties.RedEngineType, arguments );

            string customArguments = await debuggerProperties.RedScriptsCommandLineArguments.GetEvaluatedValueAtEndAsync();
            string saveFile = await debuggerProperties.SaveFile.GetEvaluatedValueAtEndAsync();

            if ( !string.IsNullOrWhiteSpace( saveFile ) )
            {
                arguments.AddArgument("save", saveFile);
            }

            if ( string.IsNullOrWhiteSpace( customArguments ) )
            {
                return arguments.Formatted;
            }

            return $"{arguments.Formatted} {customArguments}";
        }

        public static async Task<string> ConstructDeploy( ProjectProperties properties, DefinesAccessor defines )
        {
            string appDataPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
            string configIni = "RedEngine.user.ini";
            string line;
            string dataCache = "test_prod";

            string configPath = System.IO.Path.Combine( appDataPath, "CD Projekt Red", "Red Engine", configIni );

            System.IO.StreamReader file = new System.IO.StreamReader( configPath );

            while ((line = file.ReadLine()) != null)
            {
                if (line.StartsWith("DataServerCache"))
                {
                    dataCache = GetIniVarValue(line);
                    break;
                }
            }

            Arguments arguments = new Arguments();

            arguments.AddSwitch( "build-world", string.Empty );

            ConstructRoots( defines, arguments );
            await AddGameDef( "file", properties, defines, arguments );

            arguments.AddArgument("dataCache", dataCache);

            return arguments.Formatted;
        }

        #endregion Public Functions

        #region Private Functions

        private static async Task AddSwitch( IEvaluatedProperty property, Arguments arguments )
        {
            if ( property.IncludeInCommandLine && ( await property.GetValueAsync() ).Equals( true ) )
            {
                arguments.AddSwitch( property.Switch, property.SwitchPrefix );
            }
        }

        private static async Task AddArgument( IEvaluatedProperty property, Arguments arguments )
        {
            if ( property.IncludeInCommandLine )
            {
                string value = await property.GetEvaluatedValueAtEndAsync();

                if ( !string.IsNullOrWhiteSpace( value ) )
                {
                    arguments.AddArgument( property.Switch, value, property.SwitchPrefix );
                }
            }
        }

        private static async Task AddGameDef( string name, ProjectProperties properties, DefinesAccessor defines, Arguments arguments )
        {
            RedScriptsProjectDeploy deployProperties = await properties.GetRedScriptsProjectDeployPropertiesAsync();

            string gamedef = await GetDepotPath( deployProperties.GameDef, defines );
            if ( !string.IsNullOrEmpty( gamedef ) )
            {
                arguments.AddArgument( name, gamedef );
            }
        }

        private static async Task<string> GetDepotPath( IEvaluatedProperty property, DefinesAccessor defines )
        {
            string absolutePath = await property.GetEvaluatedValueAtEndAsync();

            if ( !string.IsNullOrWhiteSpace( absolutePath ) && absolutePath != property.DefaultValue )
            {
                string workspacePath = defines.ActiveWorkspaceRoot;

                if ( !string.IsNullOrEmpty( workspacePath ) )
                {
                    PathConverter converter = new PathConverter();
                    await converter.Load( workspacePath );

                    return converter.ToResourcePath( absolutePath );
                }
            }

            return null;
        }

        private static void ConstructRoots( DefinesAccessor defines, Arguments arguments )
        {
            string packagePath = defines.ActiveEnginePackagePath;
            string workspacePath = defines.ActiveWorkspaceRoot;

            if ( !string.IsNullOrEmpty( packagePath ) )
            {
                string path = System.IO.Path.Combine( packagePath, "engine" );
                arguments.AddArgument( "engineRoot", path );
            }
            else if ( !string.IsNullOrEmpty( workspacePath ) )
            {
                string path = System.IO.Path.Combine( workspacePath, "engine" );
                arguments.AddArgument( "engineRoot", path );
            }

            if ( !string.IsNullOrEmpty( workspacePath ) )
            {
                string path = System.IO.Path.Combine( workspacePath, "r6" );
                arguments.AddArgument( "gameRoot", path );
            }
        }

        #endregion Private Functions
    }
}
